/*******************************************************************************
 * This file is part of Crunch Network.
 *
 * Crunch Network is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later version.
 *
 * Crunch Network is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with
 * Crunch Network.  If not, see <http://www.gnu.org/licenses/>.
 ******************************************************************************/

package com.crunch.network.util;

/**
 * Used to calculate a moving average with a fixed window size.
 */
public class FixedWindowMovingAverage {
	/**
	 * Constructs the moving average.
	 *
	 * @param   windowSize the size of the window of data points.
	 */
	public FixedWindowMovingAverage(int windowSize) {
		if (windowSize <= 0) {
			throw new IllegalArgumentException("Invalid window size");
		}

		window = new float[windowSize];
		clear();
	}

	/**
	 * Removes all data points and resets the average.
	 */
	public void clear() {
		average = 0.0f;
		variance = 0.0f;
		offset = 0;
		dataPoints = 0;
	}

	/**
	 * Adds a data point and shifts the window forward by 1, recomputing average and variance.
	 *
	 * @param   value the data point to add.
	 */
	public void addDataPoint(float value) {
		window[offset] = value;
		offset = (offset + 1 == window.length) ? 0 : offset + 1;
		if (dataPoints < window.length) {
			++dataPoints;
		}

		// offset always is reset to 0, so we can just loop from 0 to dataPoints-1
		// i.e. there will never be "holes" in the window

		float invDataPoints = 1.0f / (float) dataPoints;

		// compute average
		average = 0.0f;
		for (int i = 0; i < dataPoints; ++i) {
			average += window[i];
		}
		average *= invDataPoints;

		// compute variance
		variance = 0.0f;
		for (int i = 0; i < dataPoints; ++i) {
			float diff = window[i] - average;
			variance += diff*diff;
		}
		variance *= invDataPoints;
	}

	/**
	 * Returns the current average for the window.
	 *
	 * @return  the current average.
	 */
	public float getAverage() {
		return average;
	}

	/**
	 * Returns the current variance for the window.
	 *
	 * @return  the current variance.
	 */
	public float getVariance() {
		return variance;
	}

	/**
	 * Returns the current number of data points. This value does not go above the initially specified window size.
	 *
	 * @return  the current number of data points.
	 */
	public int getDataPointCount() {
		return dataPoints;
	}

	/**
	 * Returns the {@code i}th most recent data point. {@code i} must be less than the the value returned by calling
	 * {@link #getDataPointCount()}.
	 *
	 * @param   i the data point to return, counting back from the most recent one added.
	 * @return  the {@code i}th most recent data point.
	 */
	public float getDataPoint(int i) {
		if (i >= dataPoints) {
			throw new IllegalArgumentException("Invalid data point");
		}

		int index = offset + window.length - i;
		if (index >= window.length) {
			index -= window.length;
		}

		return window[index];
	}

	private float[] window; // data point values
	private float average;  // the current average
	private float variance; // the current variance
	private int offset;     // the offset for the next data point
	private int dataPoints; // the number of data points
}
